package main

import (
	"context"
	"crypto/tls"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"regexp"
	"sync"
	"time"
)

const endpoint = "https://apps.apple.com/cn/app/id414478124"

// AppInfo 应用程序信息
type AppInfo struct {
	Supplier     string    // 供应商
	Size         string    // 大小
	Type         string    // 类别
	Price        string    // 价格
	AgeLevel     string    // 年龄分级
	Position     string    // 位置
	Copyright    string    // Copyright
	FeeItems     []FeeItem // App内购买项目
	DeveloperWeb string    // 开发人员网站
	AppSupport   string    // App支持
	Privacy      string    // 隐私政策

}

type FeeItem struct {
	Name  string // App内购买项目名称
	Price string // App内购买项目价格
}

func main() {
	var number int
	flag.IntVar(&number, "number", 10, "goroutine number")
	flag.Parse()
	fmt.Println("number:", number)

	fmt.Println("开始抓取...")
	ctx := context.TODO()
	ch := make(chan interface{}, number)
	exitCh := make(chan interface{})
	go func() {
		for c := range ch {
			switch t := c.(type) {
			case error:
				fmt.Println(t)
			case string:
				fmt.Println(t)
			case AppInfo:
				fmt.Println(t)
			default:
				fmt.Println(t)
			}
		}
		exitCh <- 1
	}()
	var wg sync.WaitGroup
	for i := 0; i < number; i++ {
		wg.Add(1)
		go fetch(ctx, &wg, i, ch)
	}
	wg.Wait()
	close(ch)

	<-exitCh
	fmt.Println("结束抓取...")
}

func fetch(ctx context.Context, wg *sync.WaitGroup, i int, errCh chan<- interface{}) {
	// 恢复处理,规避协程函数内部panic导致程序退出
	defer func(errCh chan<- interface{}) {
		err := recover()
		if err != nil {
			errCh <- err
			return
		}
	}(errCh)

	fmt.Println("goroutine task:", i)
	defer wg.Done()

	ctx, cancel := context.WithCancel(ctx)
	time.AfterFunc(3*time.Second, func() {
		cancel()
	})

	req, err := http.NewRequest(http.MethodGet, endpoint, nil)
	if err != nil {
		errCh <- fmt.Errorf("fetch %d http.NewRequest error:%v", i, err)
		return
	}
	req.WithContext(ctx)
	req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36")

	httpClient := http.DefaultClient
	httpClient.Transport = &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		errCh <- fmt.Errorf("fetch %d http.DefaultClient.Do error:%v", i, err)
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		errCh <- fmt.Errorf("fetch %d ioutil.ReadAll error:%v", i, err)
		return
	}

	//ioutil.WriteFile("source.html", body, 0644)

	s := string(body)
	appInfo := match(s)
	errCh <- appInfo
}

// match 使用正则匹配获取需要的应用程序信息
func match(s string) *AppInfo {
	appInfo := &AppInfo{}

	supplierRegexp := regexp.MustCompile(`
          <dt class="information-list__item__term medium-valign-top">供应商</dt>
          <dd class="information-list__item__definition">
            (.*)
          </dd>
`)
	supplierMatch := supplierRegexp.FindStringSubmatch(s)
	if len(supplierMatch) >= 1 {
		appInfo.Supplier = supplierMatch[1]
	}

	sizeRegexp := regexp.MustCompile(`<dd class="information-list__item__definition" aria-label="(.*)">(.*)</dd>`)
	sizeMatch := sizeRegexp.FindStringSubmatch(s)
	if len(sizeMatch) >= 1 {
		appInfo.Size = sizeMatch[1]
	}

	ageLevelRegexp := regexp.MustCompile(`
        <dt class="information-list__item__term medium-valign-top">年龄分级</dt>
        <dd class="information-list__item__definition">(.*)
              <span class="information-list__item__definition">(.*)</span>
        </dd>
`)
	ageLevelMatch := ageLevelRegexp.FindStringSubmatch(s)
	if len(ageLevelMatch) >= 2 {
		appInfo.AgeLevel = ageLevelMatch[1] + "," + ageLevelMatch[2]
	}

	positionRegexp := regexp.MustCompile(`
          <dt class="information-list__item__term medium-valign-top">位置</dt>
          <dd class="information-list__item__definition">(.*)</dd>
`)
	positionMatch := positionRegexp.FindStringSubmatch(s)
	if len(positionMatch) >= 1 {
		appInfo.Position = positionMatch[1]
	}

	copyrightRegexp := regexp.MustCompile(`
        <dt class="information-list__item__term medium-valign-top">Copyright</dt>
        <dd class="information-list__item__definition information-list__item__definition--copyright">(.*)</dd>
`)
	copyrightMatch := copyrightRegexp.FindStringSubmatch(s)
	if len(copyrightMatch) >= 1 {
		appInfo.Copyright = copyrightMatch[1]
	}

	priceRegexp := regexp.MustCompile(`
          <dt class="information-list__item__term medium-valign-top">价格</dt>
          <dd class="information-list__item__definition">(.*)</dd>
`)
	priceMatch := priceRegexp.FindStringSubmatch(s)
	if len(priceMatch) >= 1 {
		appInfo.Price = priceMatch[1]
	}

	feeItemsRegexp := regexp.MustCompile(`
                <li class="list-with-numbers__item">
                  <span class="list-with-numbers__item__title"><span class="truncate-single-line truncate-single-line--block">(.*)</span></span>
                  <span class="list-with-numbers__item__price small-hide medium-show-tablecell">(.*)</span>
                </li>
`)
	feeItemsMatch := feeItemsRegexp.FindAllStringSubmatch(s, -1)
	if len(feeItemsMatch) > 0 {
		for _, feeItemMatch := range feeItemsMatch {
			if len(feeItemMatch) >= 2 {
				var feeItem FeeItem
				feeItem.Name = feeItemMatch[1]
				feeItem.Price = feeItemMatch[2]
				appInfo.FeeItems = append(appInfo.FeeItems, feeItem)
			}
		}
	}

	developerWebRegexp := regexp.MustCompile(`
          <a class="link icon icon-after icon-external" data-metrics-click="{&quot;actionDetails&quot;:{&quot;type&quot;:&quot;developer&quot;},&quot;actionType&quot;:&quot;navigate&quot;,&quot;actionUrl&quot;:&quot;@@url@@&quot;,&quot;targetType&quot;:&quot;link&quot;,&quot;targetId&quot;:&quot;@@id@@&quot;}" href="(.*)">
            开发人员网站
          </a>
`)
	developerWebMatch := developerWebRegexp.FindStringSubmatch(s)
	if len(developerWebMatch) >= 1 {
		appInfo.DeveloperWeb = developerWebMatch[1]
	}

	appSupportRegexp := regexp.MustCompile(`
          <a class="link icon icon-after icon-external" data-metrics-click="{&quot;actionType&quot;:&quot;navigate&quot;,&quot;targetType&quot;:&quot;link&quot;,&quot;targetId&quot;:&quot;LinkToAppSupport&quot;}" href="(.*)">
            App 支持
          </a>
`)
	appSupportMatch := appSupportRegexp.FindStringSubmatch(s)
	if len(appSupportMatch) >= 1 {
		appInfo.AppSupport = appSupportMatch[1]
	}

	privacyRegexp := regexp.MustCompile(`
          <a class="link icon icon-after icon-external" data-metrics-click="{&quot;actionType&quot;:&quot;navigate&quot;,&quot;targetType&quot;:&quot;link&quot;,&quot;targetId&quot;:&quot;LinkToPrivacyPolicy&quot;}" href="(.*)">
            隐私政策
          </a>
`)
	privacyMatch := privacyRegexp.FindStringSubmatch(s)
	if len(privacyMatch) >= 1 {
		appInfo.Privacy = privacyMatch[1]
	}

	return appInfo
}
